#pragma once

#ifndef _SERVER_SYSTEM_SERVER_SYSTEM_H_
#define _SERVER_SYSTEM_SERVER_SYSTEM_H_


#include "Poco\SharedPtr.h"
#include "Poco\Mutex.h"
#include "Poco\BasicEvent.h"
#include "Poco\Net\SocketAddress.h"

#include "Core\StanzaHandler.h"
#include "Core\Packages.h"
#include "Core\Definations.h"
#include "Core\SafeMutex.h"
#include "Core\LoginHelper.h"
#include "Core\QuestionLibrary.h"
#include "Core\AnswerDatabase.h"

#include <vector>

namespace ExamSystem
{
    using Poco::SharedPtr;
    using Poco::BasicEvent;
    using Poco::Net::SocketAddress;

    class CExamServerConnectionManager;
    class CExamClientConnection;

namespace Server
{

    class CSystemHandler;
    class CServerHandler;
    class CClientHandler;
    class CClientServerHandler;

    /**************************************************************************\
     Component:     CServerSystem
     Description:
        This class manage the server connections and handle events. It has a 
        server manager that can accept connections from clients or other server,
        as well as a client connection to another server.
        This class should have only one instance.
    \**************************************************************************/
    class CServerSystem : public CStanzaHandler
    {
        friend class CServerHandler;
        friend class CClientHandler;
        friend class CClientServerHandler;
    public:
        CServerSystem(void);
        ~CServerSystem(void);

    public:
        // initialize the system
        // the function must be called after setting parameters
        bool Initialize(bool flagRootServer);
        bool IsInitialized(void) const;

        // this function will only be called by sub-server
        bool InitializeDatabase(void);

        // stop server
        void Finalize(void);

        // set the listening port for server manager
        void SetListeningPort(Poco::UInt16 port);
        Poco::UInt16 GetListeningPort(void) const;

        // set the connecting address for client connection
        void SetRootServerAddress(const std::string &host, Poco::UInt16 port);

        // set the database file, in relative path
        void SetQuestionLibraryPath(const std::string &path);
        void SetAnswerDatabasePath(const std::string &path);

        // set login helper property
        void SetLoginHelperProperty(const std::string &property, const std::string &value);

        // return the version of question database
        const std::string &GetQuestionLibraryVersion(void) const;

        // return the database file name, in relative path
        const std::string &GetQuestionLibraryPath(void) const;

        // libraries
        CQuestionLibrary &GetQuestionLibrary(void);
        CAnswerDatabase &GetAnswerDatabase(void);

        // respond to exam order
        void RespondToOrder(COrderPackage &order);

        // send exam order to all connections
        void SendExamOrder(COrderPackage::ExamOrder order);
        void SendExamOrder(COrderPackage &order);
        void StartExam(void);
        void StopExam(void);

        // send exam message to all connections
        void SendExamMessage(CMessagePackage &message);
        void SendExamMessage(const std::string &message, CMessagePackage::MessageType type);

        // send exam information to all connected servers
        void SendExamInformation(CInformationPackage &information);

        // set up this server system according to received exam information
        void SetExamInformation(const ExamInformation &information);
        // get server's settings 
        ExamInformation &GetExamInformation(void);
        // apply exam parameters after setting, then send the exam information to all client-servers
        void ApplyExamInformation(void);

        // the student login, check name and number
        CLoginHelper::OperationResult StudentLogin(CClientHandler *client, const std::string &name, const std::string &number);

        // the invigilator login, check name and password
        CLoginHelper::OperationResult InvigilatorLogin(CClientHandler *client, const std::string &user, const std::string &password);

        // return whether the exam has started
        bool IsExamStart(void) const;
        bool IsExamFinished(void) const;

        // return whether the connection should be redirected, as well as the target address
        bool RedirectConnection(CSystemHandler *handler, SocketAddress &address);

        // note: these two methods are not thread-safe 
        std::string GetClientServerAddress(int idx);
        std::string GetClientAddress(int idx);  
        std::string GetClientServerAddress(const std::vector<int> &path);
        std::string GetClientAddress(const std::vector<int> &path);

        // student name and password
        typedef StudentInfo StudentInfo;
        const StudentInfo &GetStudentInfo(int idx);
        const StudentInfo &GetStudentInfo(const std::vector<int> &path);

        typedef struct ClientInfo
        {
            unsigned int clientID;
            SocketAddress clientAddress;
            StudentInfo loginStudent;
        } ClientInfo;

        typedef struct ServerInfo
        {
            unsigned int serverID;
            SocketAddress serverAddress;
            unsigned int serverRedirections;
            std::vector<SharedPtr<ServerInfo> > connectedServers;
            std::vector<SharedPtr<ClientInfo> > connectedClients;
        } ServerInfo;
 
    protected:
        // stanza handler, we only handle one type, e.g. identity
        virtual void startElement(CXMLConnection *connection, const XMLString& uri, const XMLString& localName, const XMLString& qname, const Attributes& attributes);
        virtual void endElement(CXMLConnection *connection, const XMLString& uri, const XMLString& localName, const XMLString& qname);
        virtual void characters(CXMLConnection *connection, const XMLChar ch[], int start, int length);

    protected:
        // event handlers
        void OnSystemIDUpdated(const void *, void *const&);

        void OnClientConnectionClosed(const void* pSender, int &id);
        void OnClientConnectionError(const void *pSender, void *const& pConnection);
        void OnClientConnectionInfoUpdated(const void *pServer, CConnectionInfoPackage &info);
        void OnClientServerReady(const void *pServer, void *const&);

        void OnServerConnectionClosed(const void* pSender, int &id);
        void OnServerConnectionDisconnected(const void* pSender, int &id);
        void OnServerConnectionReconnected(const void *pSender, int &id);
        void OnServerConnectionRedirected(const void *pSender, SocketAddress &targetAddress);
        void OnServerConnectionVersionCheckFailed(const void *pSender, void *const& pConnection);
        void OnServerConnectionReady(const void *, void *const& );
    
    protected:
        // start the server and prepare to accept connections
        void StartServer(void);
        // stop server and close all connections
        void StopServer(void);

        // connect to an other server
        bool ConnectServer(void);
        bool CreateHandler(void);
        void DisconnectServer(void);

        // inform root server that a connection lost
        void SendDisconnectInfo(bool server, int idx);

        void SendAllConnectionInfo(void);
        void SendConnectionInfo(std::vector<int> &path, CConnectionInfoPackage &info, SharedPtr<ServerInfo> &serverInfo);

        SharedPtr<ServerInfo> GetClientServerInfo(const std::vector<int> &rpath, std::vector<int> &path, bool server, bool newConnection);

        // generate questions
        void GenerateQuestions(std::vector<int> questionIds[QuestionTypeCount]);

        // merge received answer database with current one
        bool MergeAnswerDatabase(const std::string &name);
        
        // send answer data base to root server
        void SendAnswerDatabase(void);

    public:
        // events
        BasicEvent<CServerSystem *const> eventServerConnectionConnected;
        BasicEvent<CServerSystem *const> eventServerConnectionDisconnected;
        BasicEvent<CServerSystem *const> eventServerConnectionClosed;

        BasicEvent<Poco::UInt16> eventServerStartListening;

        BasicEvent<const int> eventClientServerConnected;
        BasicEvent<const int> eventClientServerInfoUpdated;
        BasicEvent<const int> eventClientServerDisconnected;

        BasicEvent<const int> eventClientConnected;
        BasicEvent<const int> eventClientDisconnected;

        BasicEvent<const int> eventStudentLogin;

        BasicEvent<std::vector<int> > eventClientServerConnectedInfo;
        BasicEvent<std::vector<int> > eventClientServerDisconnectedInfo;
        BasicEvent<std::vector<int> > eventClientConnectedInfo;
        BasicEvent<std::vector<int> > eventClientDisconnectedInfo;

        BasicEvent<const int> eventExamStart;
        BasicEvent<const int> eventExamStop;

    protected:
        SharedPtr<CExamServerConnectionManager> m_serverConnectionManager;

        // connection to (root) server
        SharedPtr<CExamClientConnection> m_serverConnection;
        // handler for the connection to server
        SharedPtr<CServerHandler> m_serverConnectionHandler;
        // zombies
        SharedPtr<CExamClientConnection> m_zombieServerConnection;

        // listening port
        Poco::UInt16 m_listeningPort;

        // connecting address
        SocketAddress m_rootServerAddress;

        // connections
        SharedPtr<ServerInfo> m_serverInfo;
        std::vector<int> m_clientServerConnection;
        std::vector<int> m_clientConnection;
        std::vector<SharedPtr<CClientHandler> > m_clientHandlers;
        std::vector<SharedPtr<CClientServerHandler> > m_clientServerHandlers;

        // locks
        CSafeMutex m_connectionLock;
        CSafeMutex m_databaseLock;

        // this flag identifies whether this is a root server
        bool m_flagRootServer;

        // this flag identifies whether the connection has been redirected
        bool m_flagConnectionRedirected;

        bool m_flagValuntaryClosure;

        // the database version
        std::string m_questionLibraryVersion;

        // database file, in related path
        std::string m_questionLibraryFile;
        CQuestionLibrary m_questionLibrary;
        std::vector<int> m_questionIDs[QuestionTypeCount];

        // answer database
        std::string m_answerDatabaseFile;
        CAnswerDatabase m_answerDatabase;

        // current package
        SharedPtr<CPackage> m_currentPackage;

        // the exam information
        ExamInformation m_examInformation;

        // databases
        CLoginHelper m_loginHelper;

        bool m_flagInitialized;
    };

    extern CServerSystem theServerSystem;
}
}

#endif